}
}
+static gboolean
+save_to_file_callback (const gchar *buf,
+ gsize count,
+ GError **error,
+ gpointer data)
+{
+ FILE *filehandle = data;
+ gsize n;
+
+ n = fwrite (buf, 1, count, filehandle);
+ if (n != count) {
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ _("Error writing to image file: %s"),
+ g_strerror (errno));
+ return FALSE;
+ }
+ return TRUE;
+}
+
static gboolean
gdk_pixbuf_real_save (GdkPixbuf *pixbuf,
FILE *filehandle,
if (!_gdk_pixbuf_load_module (image_module, error))
return FALSE;
- if (image_module->save == NULL) {
+ if (image_module->save) {
+ /* save normally */
+ return (* image_module->save) (filehandle, pixbuf,
+ keys, values,
+ error);
+ }
+ else if (image_module->save_to_callback) {
+ /* save with simple callback */
+ return (* image_module->save_to_callback) (save_to_file_callback,
+ filehandle, pixbuf,
+ keys, values,
+ error);
+ }
+ else {
+ /* can't save */
+ g_set_error (error,
+ GDK_PIXBUF_ERROR,
+ GDK_PIXBUF_ERROR_UNSUPPORTED_OPERATION,
+ _("This build of gdk-pixbuf does not support saving the image format: %s"),
+ type);
+ return FALSE;
+ }
+}
+
+#define TMP_FILE_BUF_SIZE 4096
+
+static gboolean
+save_to_callback_with_tmp_file (GdkPixbufModule *image_module,
+ GdkPixbuf *pixbuf,
+ GdkPixbufSaveFunc save_func,
+ gpointer user_data,
+ gchar **keys,
+ gchar **values,
+ GError **error)
+{
+ int fd;
+ FILE *f = NULL;
+ gboolean retval = FALSE;
+ gchar *buf = NULL;
+ gsize n;
+ gchar *filename = NULL;
+
+ buf = g_try_malloc (TMP_FILE_BUF_SIZE);
+ if (buf == NULL) {
+ g_set_error (error,
+ GDK_PIXBUF_ERROR,
+ GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+ _("Insufficient memory to save image to callback"));
+ goto end;
+ }
+
+ fd = g_file_open_tmp ("gdkpixbuf-save-tmp.XXXXXX", &filename, error);
+ if (fd == -1)
+ goto end;
+ f = fdopen (fd, "wb+");
+ if (f == NULL) {
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ _("Failed to open temporary file"));
+ goto end;
+ }
+ if (!(* image_module->save) (f, pixbuf, keys, values, error))
+ goto end;
+ rewind (f);
+ for (;;) {
+ n = fread (buf, 1, TMP_FILE_BUF_SIZE, f);
+ if (n > 0) {
+ if (!save_func (buf, n, error, user_data))
+ goto end;
+ }
+ if (n != TMP_FILE_BUF_SIZE)
+ break;
+ }
+ if (ferror (f)) {
+ g_set_error (error,
+ G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ _("Failed to read from temporary file"));
+ goto end;
+ }
+ retval = TRUE;
+
+ end:
+ /* cleanup and return retval */
+ if (f)
+ fclose (f);
+ if (filename) {
+ unlink (filename);
+ g_free (filename);
+ }
+ g_free (buf);
+
+ return retval;
+}
+
+static gboolean
+gdk_pixbuf_real_save_to_callback (GdkPixbuf *pixbuf,
+ GdkPixbufSaveFunc save_func,
+ gpointer user_data,
+ const char *type,
+ gchar **keys,
+ gchar **values,
+ GError **error)
+{
+ GdkPixbufModule *image_module = NULL;
+
+ image_module = _gdk_pixbuf_get_named_module (type, error);
+
+ if (image_module == NULL)
+ return FALSE;
+
+ if (image_module->module == NULL)
+ if (!_gdk_pixbuf_load_module (image_module, error))
+ return FALSE;
+
+ if (image_module->save_to_callback) {
+ /* save normally */
+ return (* image_module->save_to_callback) (save_func, user_data,
+ pixbuf, keys, values,
+ error);
+ }
+ else if (image_module->save) {
+ /* use a temporary file */
+ return save_to_callback_with_tmp_file (image_module, pixbuf,
+ save_func, user_data,
+ keys, values,
+ error);
+ }
+ else {
+ /* can't save */
g_set_error (error,
GDK_PIXBUF_ERROR,
GDK_PIXBUF_ERROR_UNSUPPORTED_OPERATION,
type);
return FALSE;
}
-
- return (* image_module->save) (filehandle, pixbuf,
- keys, values,
- error);
}
* @error: return location for error, or %NULL
* @Varargs: list of key-value save options
*
- * Saves pixbuf to a file in @type, which is currently "jpeg", "png" or
- * "ico". If @error is set, %FALSE will be returned. Possible errors include
+ * Saves pixbuf to a file in format @type. By default, "jpeg", "png" and
+ * "ico" are possible file formats to save in, but more formats may be
+ * installed. The list of all writable formats can be determined in the
+ * following way:
+ *
+ * <informalexample><programlisting>
+ * void add_if_writable (GdkPixbufFormat *data, GSList **list)
+ * {
+ * if (gdk_pixbuf_format_is_writable (data))
+ * *list = g_slist_prepend (*list, data);
+ * }
+ *
+ * GSList *formats = gdk_pixbuf_get_formats (<!-- -->);
+ * GSList *writable_formats = NULL;
+ * g_slist_foreach (formats, add_if_writable, &writable_formats);
+ * g_slist_free (formats);
+ * </programlisting></informalexample>
+ *
+ * If @error is set, %FALSE will be returned. Possible errors include
* those in the #GDK_PIXBUF_ERROR domain and those in the #G_FILE_ERROR domain.
*
* The variable argument list should be %NULL-terminated; if not empty,
* @option_values: values for named options
* @error: return location for error, or %NULL
*
- * Saves pixbuf to a file in @type, which is currently "jpeg" or "png".
- * If @error is set, %FALSE will be returned. See gdk_pixbuf_save () for more
- * details.
+ * Saves pixbuf to a file in @type, which is currently "jpeg", "png" or "ico".
+ * If @error is set, %FALSE will be returned.
+ * See gdk_pixbuf_save () for more details.
*
* Return value: whether an error was set
**/
return TRUE;
}
+/**
+ * gdk_pixbuf_save_to_callback:
+ * @pixbuf: a #GdkPixbuf.
+ * @save_func: a function that is called to save each block of data that
+ * the save routine generates.
+ * @user_data: user data to pass to the save function.
+ * @type: name of file format.
+ * @error: return location for error, or %NULL
+ * @Varargs: list of key-value save options
+ *
+ * Saves pixbuf in format @type by feeding the produced data to a
+ * callback. Can be used when you want to store the image to something
+ * other than a file, such as an in-memory buffer or a socket.
+ * If @error is set, %FALSE will be returned. Possible errors
+ * include those in the #GDK_PIXBUF_ERROR domain and whatever the save
+ * function generates.
+ *
+ * See gdk_pixbuf_save() for more details.
+ *
+ * Return value: whether an error was set
+ *
+ * Since: 2.4
+ **/
+gboolean
+gdk_pixbuf_save_to_callback (GdkPixbuf *pixbuf,
+ GdkPixbufSaveFunc save_func,
+ gpointer user_data,
+ const char *type,
+ GError **error,
+ ...)
+{
+ gchar **keys = NULL;
+ gchar **values = NULL;
+ va_list args;
+ gboolean result;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ va_start (args, error);
+
+ collect_save_options (args, &keys, &values);
+
+ va_end (args);
+
+ result = gdk_pixbuf_save_to_callbackv (pixbuf, save_func, user_data,
+ type, keys, values,
+ error);
+
+ g_strfreev (keys);
+ g_strfreev (values);
+
+ return result;
+}
+
+/**
+ * gdk_pixbuf_save_to_callbackv:
+ * @pixbuf: a #GdkPixbuf.
+ * @save_func: a function that is called to save each block of data that
+ * the save routine generates.
+ * @user_data: user data to pass to the save function.
+ * @type: name of file format.
+ * @option_keys: name of options to set, %NULL-terminated
+ * @option_values: values for named options
+ * @error: return location for error, or %NULL
+ *
+ * Saves pixbuf to a callback in format @type, which is currently "jpeg",
+ * "png" or "ico". If @error is set, %FALSE will be returned. See
+ * gdk_pixbuf_save_to_callback () for more details.
+ *
+ * Return value: whether an error was set
+ *
+ * Since: 2.4
+ **/
+gboolean
+gdk_pixbuf_save_to_callbackv (GdkPixbuf *pixbuf,
+ GdkPixbufSaveFunc save_func,
+ gpointer user_data,
+ const char *type,
+ char **option_keys,
+ char **option_values,
+ GError **error)
+{
+ gboolean result;
+
+
+ g_return_val_if_fail (save_func != NULL, FALSE);
+ g_return_val_if_fail (type != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ result = gdk_pixbuf_real_save_to_callback (pixbuf,
+ save_func, user_data, type,
+ option_keys, option_values,
+ error);
+
+ if (!result) {
+ g_return_val_if_fail (error == NULL || *error != NULL, FALSE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * gdk_pixbuf_save_to_buffer:
+ * @pixbuf: a #GdkPixbuf.
+ * @buffer: location to receive a pointer to the new buffer.
+ * @buffer_size: location to receive the size of the new buffer.
+ * @type: name of file format.
+ * @error: return location for error, or %NULL
+ * @Varargs: list of key-value save options
+ *
+ * Saves pixbuf to a new buffer in format @type, which is currently "jpeg",
+ * "png" or "ico". This is a convenience function that uses
+ * gdk_pixbuf_save_to_callback() to do the real work. Note that the buffer
+ * is not nul-terminated and may contain embedded nuls.
+ * If @error is set, %FALSE will be returned and @string will be set to
+ * %NULL. Possible errors include those in the #GDK_PIXBUF_ERROR
+ * domain.
+ *
+ * See gdk_pixbuf_save() for more details.
+ *
+ * Return value: whether an error was set
+ *
+ * Since: 2.4
+ **/
+gboolean
+gdk_pixbuf_save_to_buffer (GdkPixbuf *pixbuf,
+ gchar **buffer,
+ gsize *buffer_size,
+ const char *type,
+ GError **error,
+ ...)
+{
+ gchar **keys = NULL;
+ gchar **values = NULL;
+ va_list args;
+ gboolean result;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ va_start (args, error);
+
+ collect_save_options (args, &keys, &values);
+
+ va_end (args);
+
+ result = gdk_pixbuf_save_to_bufferv (pixbuf, buffer, buffer_size,
+ type, keys, values,
+ error);
+
+ g_strfreev (keys);
+ g_strfreev (values);
+
+ return result;
+}
+
+struct SaveToBufferData {
+ gchar *buffer;
+ gsize len, max;
+};
+
+static gboolean
+save_to_buffer_callback (const gchar *data,
+ gsize count,
+ GError **error,
+ gpointer user_data)
+{
+ struct SaveToBufferData *sdata = user_data;
+ gchar *new_buffer;
+ gsize new_max;
+
+ if (sdata->len + count > sdata->max) {
+ new_max = MAX (sdata->max*2, sdata->len + count);
+ new_buffer = g_try_realloc (sdata->buffer, new_max);
+ if (!new_buffer) {
+ g_set_error (error,
+ GDK_PIXBUF_ERROR,
+ GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+ _("Insufficient memory to save image into a buffer"));
+ return FALSE;
+ }
+ sdata->buffer = new_buffer;
+ sdata->max = new_max;
+ }
+ memcpy (sdata->buffer + sdata->len, data, count);
+ sdata->len += count;
+ return TRUE;
+}
+
+/**
+ * gdk_pixbuf_save_to_bufferv:
+ * @pixbuf: a #GdkPixbuf.
+ * @buffer: location to receive a pointer to the new buffer.
+ * @buffer_size: location to receive the size of the new buffer.
+ * @type: name of file format.
+ * @option_keys: name of options to set, %NULL-terminated
+ * @option_values: values for named options
+ * @error: return location for error, or %NULL
+ *
+ * Saves pixbuf to a new buffer in format @type, which is currently "jpeg",
+ * "png" or "ico". See gdk_pixbuf_save_to_buffer() for more details.
+ *
+ * Return value: whether an error was set
+ *
+ * Since: 2.4
+ **/
+gboolean
+gdk_pixbuf_save_to_bufferv (GdkPixbuf *pixbuf,
+ gchar **buffer,
+ gsize *buffer_size,
+ const char *type,
+ char **option_keys,
+ char **option_values,
+ GError **error)
+{
+ static const gint initial_max = 1024;
+ struct SaveToBufferData sdata;
+
+ *buffer = NULL;
+ *buffer_size = 0;
+
+ sdata.buffer = g_try_malloc (initial_max);
+ sdata.max = initial_max;
+ sdata.len = 0;
+ if (!sdata.buffer) {
+ g_set_error (error,
+ GDK_PIXBUF_ERROR,
+ GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+ _("Insufficient memory to save image into a buffer"));
+ return FALSE;
+ }
+
+ if (!gdk_pixbuf_save_to_callbackv (pixbuf,
+ save_to_buffer_callback, &sdata,
+ type, option_keys, option_values,
+ error)) {
+ g_free (sdata.buffer);
+ return FALSE;
+ }
+
+ *buffer = sdata.buffer;
+ *buffer_size = sdata.len;
+ return TRUE;
+}
+
/**
* gdk_pixbuf_format_get_name:
* @format: a #GdkPixbufFormat
return TRUE;
}
+/* Save */
+
+#define TO_FUNCTION_BUF_SIZE 4096
+
+typedef struct {
+ struct jpeg_destination_mgr pub;
+ JOCTET *buffer;
+ GdkPixbufSaveFunc save_func;
+ gpointer user_data;
+ GError **error;
+} ToFunctionDestinationManager;
+
+void
+to_callback_init (j_compress_ptr cinfo)
+{
+ ToFunctionDestinationManager *destmgr;
+
+ destmgr = (ToFunctionDestinationManager*) cinfo->dest;
+ destmgr->pub.next_output_byte = destmgr->buffer;
+ destmgr->pub.free_in_buffer = TO_FUNCTION_BUF_SIZE;
+}
+
+static void
+to_callback_do_write (j_compress_ptr cinfo, gsize length)
+{
+ ToFunctionDestinationManager *destmgr;
+
+ destmgr = (ToFunctionDestinationManager*) cinfo->dest;
+ if (!destmgr->save_func (destmgr->buffer,
+ length,
+ destmgr->error,
+ destmgr->user_data)) {
+ struct error_handler_data *errmgr;
+
+ errmgr = (struct error_handler_data *) cinfo->err;
+ /* Use a default error message if the callback didn't set one,
+ * which it should have.
+ */
+ if (errmgr->error && *errmgr->error == NULL) {
+ g_set_error (errmgr->error,
+ GDK_PIXBUF_ERROR,
+ GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+ "write function failed");
+ }
+ siglongjmp (errmgr->setjmp_buffer, 1);
+ g_assert_not_reached ();
+ }
+}
+
+static guchar
+to_callback_empty_output_buffer (j_compress_ptr cinfo)
+{
+ ToFunctionDestinationManager *destmgr;
+
+ destmgr = (ToFunctionDestinationManager*) cinfo->dest;
+ to_callback_do_write (cinfo, TO_FUNCTION_BUF_SIZE);
+ destmgr->pub.next_output_byte = destmgr->buffer;
+ destmgr->pub.free_in_buffer = TO_FUNCTION_BUF_SIZE;
+ return TRUE;
+}
+
+void
+to_callback_terminate (j_compress_ptr cinfo)
+{
+ ToFunctionDestinationManager *destmgr;
+
+ destmgr = (ToFunctionDestinationManager*) cinfo->dest;
+ to_callback_do_write (cinfo, TO_FUNCTION_BUF_SIZE - destmgr->pub.free_in_buffer);
+}
+
static gboolean
-gdk_pixbuf__jpeg_image_save (FILE *f,
- GdkPixbuf *pixbuf,
- gchar **keys,
- gchar **values,
- GError **error)
+real_save_jpeg (GdkPixbuf *pixbuf,
+ gchar **keys,
+ gchar **values,
+ GError **error,
+ gboolean to_callback,
+ FILE *f,
+ GdkPixbufSaveFunc save_func,
+ gpointer user_data)
{
/* FIXME error handling is broken */
int w, h = 0;
int rowstride = 0;
struct error_handler_data jerr;
+ ToFunctionDestinationManager to_callback_destmgr;
+
+ to_callback_destmgr.buffer = NULL;
if (keys && *keys) {
gchar **kiter = keys;
pixels = gdk_pixbuf_get_pixels (pixbuf);
g_return_val_if_fail (pixels != NULL, FALSE);
- /* allocate a small buffer to convert image data */
+ /* Allocate a small buffer to convert image data,
+ * and a larger buffer if doing to_callback save.
+ */
buf = g_try_malloc (w * 3 * sizeof (guchar));
if (!buf) {
g_set_error (error,
_("Couldn't allocate memory for loading JPEG file"));
return FALSE;
}
+ if (to_callback) {
+ to_callback_destmgr.buffer = g_try_malloc (TO_FUNCTION_BUF_SIZE);
+ if (!to_callback_destmgr.buffer) {
+ g_set_error (error,
+ GDK_PIXBUF_ERROR,
+ GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+ _("Couldn't allocate memory for loading JPEG file"));
+ return FALSE;
+ }
+ }
/* set up error handling */
jerr.pub.error_exit = fatal_error_handler;
if (sigsetjmp (jerr.setjmp_buffer, 1)) {
jpeg_destroy_compress (&cinfo);
g_free (buf);
+ g_free (to_callback_destmgr.buffer);
return FALSE;
}
/* setup compress params */
jpeg_create_compress (&cinfo);
- jpeg_stdio_dest (&cinfo, f);
+ if (to_callback) {
+ to_callback_destmgr.pub.init_destination = to_callback_init;
+ to_callback_destmgr.pub.empty_output_buffer = to_callback_empty_output_buffer;
+ to_callback_destmgr.pub.term_destination = to_callback_terminate;
+ to_callback_destmgr.error = error;
+ to_callback_destmgr.save_func = save_func;
+ to_callback_destmgr.user_data = user_data;
+ cinfo.dest = (struct jpeg_destination_mgr*) &to_callback_destmgr;
+ } else {
+ jpeg_stdio_dest (&cinfo, f);
+ }
cinfo.image_width = w;
cinfo.image_height = h;
cinfo.input_components = 3;
jpeg_finish_compress (&cinfo);
jpeg_destroy_compress(&cinfo);
g_free (buf);
+ g_free (to_callback_destmgr.buffer);
return TRUE;
}
+static gboolean
+gdk_pixbuf__jpeg_image_save (FILE *f,
+ GdkPixbuf *pixbuf,
+ gchar **keys,
+ gchar **values,
+ GError **error)
+{
+ return real_save_jpeg (pixbuf, keys, values, error,
+ FALSE, f, NULL, NULL);
+}
+
+static gboolean
+gdk_pixbuf__jpeg_image_save_to_callback (GdkPixbufSaveFunc save_func,
+ gpointer user_data,
+ GdkPixbuf *pixbuf,
+ gchar **keys,
+ gchar **values,
+ GError **error)
+{
+ return real_save_jpeg (pixbuf, keys, values, error,
+ TRUE, NULL, save_func, user_data);
+}
+
void
MODULE_ENTRY (jpeg, fill_vtable) (GdkPixbufModule *module)
{
module->stop_load = gdk_pixbuf__jpeg_image_stop_load;
module->load_increment = gdk_pixbuf__jpeg_image_load_increment;
module->save = gdk_pixbuf__jpeg_image_save;
+ module->save_to_callback = gdk_pixbuf__jpeg_image_save_to_callback;
}
void
/* Save */
-static gboolean
-gdk_pixbuf__png_image_save (FILE *f,
- GdkPixbuf *pixbuf,
- gchar **keys,
- gchar **values,
- GError **error)
+typedef struct {
+ GdkPixbufSaveFunc save_func;
+ gpointer user_data;
+ GError **error;
+} SaveToFunctionIoPtr;
+
+static void
+png_save_to_callback_write_func (png_structp png_ptr,
+ png_bytep data,
+ png_size_t length)
+{
+ SaveToFunctionIoPtr *ioptr = png_get_io_ptr (png_ptr);
+
+ if (!ioptr->save_func (data, length, ioptr->error, ioptr->user_data)) {
+ /* If save_func has already set an error, which it
+ should have done, this won't overwrite it. */
+ png_error (png_ptr, "write function failed");
+ }
+}
+
+static void
+png_save_to_callback_flush_func (png_structp png_ptr)
+{
+ ;
+}
+
+static gboolean real_save_png (GdkPixbuf *pixbuf,
+ gchar **keys,
+ gchar **values,
+ GError **error,
+ gboolean to_callback,
+ FILE *f,
+ GdkPixbufSaveFunc save_func,
+ gpointer user_data)
{
png_structp png_ptr;
png_infop info_ptr;
int bpc;
int num_keys;
gboolean success = TRUE;
+ SaveToFunctionIoPtr to_callback_ioptr;
num_keys = 0;
png_set_text (png_ptr, info_ptr, text_ptr, num_keys);
}
- png_init_io (png_ptr, f);
+ if (to_callback) {
+ to_callback_ioptr.save_func = save_func;
+ to_callback_ioptr.user_data = user_data;
+ to_callback_ioptr.error = error;
+ png_set_write_fn (png_ptr, &to_callback_ioptr,
+ png_save_to_callback_write_func,
+ png_save_to_callback_flush_func);
+ } else {
+ png_init_io (png_ptr, f);
+ }
if (has_alpha) {
png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
return success;
}
+static gboolean
+gdk_pixbuf__png_image_save (FILE *f,
+ GdkPixbuf *pixbuf,
+ gchar **keys,
+ gchar **values,
+ GError **error)
+{
+ return real_save_png (pixbuf, keys, values, error,
+ FALSE, f, NULL, NULL);
+}
+
+static gboolean
+gdk_pixbuf__png_image_save_to_callback (GdkPixbufSaveFunc save_func,
+ gpointer user_data,
+ GdkPixbuf *pixbuf,
+ gchar **keys,
+ gchar **values,
+ GError **error)
+{
+ return real_save_png (pixbuf, keys, values, error,
+ TRUE, NULL, save_func, user_data);
+}
+
void
MODULE_ENTRY (png, fill_vtable) (GdkPixbufModule *module)
{
module->stop_load = gdk_pixbuf__png_image_stop_load;
module->load_increment = gdk_pixbuf__png_image_load_increment;
module->save = gdk_pixbuf__png_image_save;
+ module->save_to_callback = gdk_pixbuf__png_image_save_to_callback;
}
void